package com.scau.os.fileSystem.directory;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.scau.os.extension.EventSystem;
import com.scau.os.fileSystem.FolderTree.FolderPane;
import com.scau.os.fileSystem.memory.Disk;
import com.scau.os.fileSystem.memory.FAT;
import com.scau.os.fileSystem.other.MyFileSystemException;

public enum FileManager {

    INSTANCE;

    public void createDirectory(String path) throws Exception {
        checkPathForDirectory(path);

        String name = path.substring(path.lastIndexOf("/") + 1);
        String type = DirectoryEntry.DIRECTORY_TYPE;
        byte attributes = DirectoryEntry.IS_DIRECTORY;
        byte startBlock = 0;
        byte length = 0;
        Property childProperty = new Property(name, type, attributes, startBlock, length);
        Directory childDirectory = new Directory(path, childProperty);

        Directory fatherDirectory = childDirectory.getFatherDirectory();

        if (fatherDirectory.exist(childDirectory) == true) {
            throw new MyFileSystemException("目录已存在");
        }

        startBlock = (byte) FAT.INSTANCE.allocateBlock();
        Directory.initDirectoryEntry(startBlock);
        childProperty.setStartBlock(startBlock);
        childDirectory.setProperty(childProperty);

        writeEntry(fatherDirectory.getAvailableBlock(), childDirectory);

        FolderPane.getInstance().refresh(fatherDirectory);
        EventSystem.instance.SendEvent("showConsoleInfo", "Directory created: " + path);
    }

    public void removeDirectory(String path) throws Exception {
        checkPathForDirectory(path);

        Directory directory = new Directory(path);

        if (directory.isEmpty()) {
            deleteDirectory(path);
            EventSystem.instance.SendEvent("showConsoleInfo", "Directory removed: " + path);
        } else {
            throw new MyFileSystemException("目录不为空");
        }
    }

    public void moveDirectory(String sourceDirectoryPath, String destinationDirectoryPath) throws Exception {
        checkPathForDirectory(sourceDirectoryPath);
        checkPathForDirectory(destinationDirectoryPath);

        Directory sourceDirectory = new Directory(sourceDirectoryPath);
        Directory destinationDirectory = new Directory(destinationDirectoryPath);

        Directory newDirectory = new Directory(destinationDirectoryPath + "/" + sourceDirectory.getProperty().getName(),
                sourceDirectory.getProperty());

        if (destinationDirectory.exist(newDirectory) == true) {
            throw new MyFileSystemException("目录已存在");
        }

        removeEntry(sourceDirectory.getFatherDirectory().getBlockOfEntry(sourceDirectory), sourceDirectory);
        writeEntry(destinationDirectory.getAvailableBlock(), newDirectory);

        FolderPane.getInstance().refresh(sourceDirectory.getFatherDirectory());
        FolderPane.getInstance().refresh(destinationDirectory);
        EventSystem.instance.SendEvent("showConsoleInfo", "Directory moved: " + sourceDirectoryPath + " -> "
                + newDirectory.getPath().getAbsolutePath());
    }

    public void deleteDirectory(String path) throws Exception {
        checkPathForDirectory(path);

        Directory childDirectory = new Directory(path);
        Directory fatherDirectory = childDirectory.getFatherDirectory();

        int childDirectoryBlock = fatherDirectory.getBlockOfEntry(childDirectory);
        if (childDirectoryBlock == 0) {
            throw new MyFileSystemException("目录不存在");
        }

        List<DirectoryEntry> children = childDirectory.getChildren();
        for (DirectoryEntry child : children) {
            if (child.isDirectory()) {
                deleteDirectory(child.getPath().getAbsolutePath());
            } else {
                FAT.INSTANCE.freeBlock(child.getProperty().getStartBlock());
            }
        }

        FAT.INSTANCE.freeBlock(childDirectory.getProperty().getStartBlock());
        removeEntry(childDirectoryBlock, childDirectory);

        FolderPane.getInstance().refresh(fatherDirectory);
        EventSystem.instance.SendEvent("showConsoleInfo", "Directory deleted: " + path);
    }

    public void changeDirectoryName(String path, String newName) throws Exception {
        checkPathForDirectory(path);

        Directory directory = new Directory(path);
        Directory fatherDirectory = directory.getFatherDirectory();

        int directoryBlock = fatherDirectory.getBlockOfEntry(directory);
        if (directoryBlock == 0) {
            throw new MyFileSystemException("目录不存在");
        }

        removeEntry(directoryBlock, directory);
        directory.getProperty().setName(newName);
        writeEntry(directoryBlock, directory);

        FolderPane.getInstance().refresh(fatherDirectory);
        EventSystem.instance.SendEvent("showConsoleInfo", "Directory renamed: " + path + " -> " + newName);
    }

    public void createFile(String path) throws Exception {
        checkPathForFile(path);

        String name = path.substring(path.lastIndexOf("/") + 1, path.lastIndexOf("."));
        String type = path.substring(path.lastIndexOf(".") + 1);
        byte attributes = File.IS_COMMON_FILE;
        byte startBlock = 0;
        byte length = 1;
        Property childProperty = new Property(name, type, attributes, startBlock, length);
        File childFile = new File(path, childProperty);

        Directory fatherDirectory = childFile.getFatherDirectory();
        if (fatherDirectory.exist(childFile) == true) {
            throw new MyFileSystemException("文件已存在");
        }

        startBlock = (byte) FAT.INSTANCE.allocateBlock();
        childProperty.setStartBlock(startBlock);
        childFile.setProperty(childProperty);

        writeEntry(fatherDirectory.getAvailableBlock(), childFile);

        FolderPane.getInstance().refresh(fatherDirectory);
        EventSystem.instance.SendEvent("showConsoleInfo", "File created: " + path);
    }

    public void removeFile(String path) throws Exception {
        checkPathForFile(path);

        File childFile = new File(path);
        Directory fatherDirectory = childFile.getFatherDirectory();

        int childFileBlock = fatherDirectory.getBlockOfEntry(childFile);
        if (childFileBlock == 0) {
            throw new MyFileSystemException("文件不存在");
        }

        FAT.INSTANCE.freeBlock(childFile.getProperty().getStartBlock());
        removeEntry(childFileBlock, childFile);

        FolderPane.getInstance().refresh(fatherDirectory);
        EventSystem.instance.SendEvent("showConsoleInfo", "File removed: " + path);
    }

    public void copyFile(String sourceFilePath, String destinationDirectoryPath) throws Exception {
        checkPathForFile(sourceFilePath);
        checkPathForDirectory(destinationDirectoryPath);

        File sourceFile = new File(sourceFilePath);
        Directory destinationDirectory = new Directory(destinationDirectoryPath);

        String name = sourceFilePath.substring(sourceFilePath.lastIndexOf("/") + 1, sourceFilePath.lastIndexOf("."));
        String type = sourceFilePath.substring(sourceFilePath.lastIndexOf(".") + 1);
        byte attributes = File.IS_COMMON_FILE;
        byte startBlock = 0;
        byte length = 1;
        Property childProperty = new Property(name, type, attributes, startBlock, length);
        File childFile = new File(destinationDirectoryPath + "/" + name + "." + type, childProperty);

        if (destinationDirectory.exist(childFile) == true) {
            throw new MyFileSystemException("文件已存在");
        }

        List<Integer> sourceBlocks = sourceFile.getBlocks();
        List<Integer> destinationBlocks = FAT.INSTANCE.allocateBlock(sourceBlocks.size());

        startBlock = destinationBlocks.get(0).byteValue();
        childProperty.setStartBlock(startBlock);
        childFile.setProperty(childProperty);

        writeEntry(destinationDirectory.getAvailableBlock(), childFile);

        for (int i = 0; i < sourceBlocks.size(); i++) {
            byte[] bytes = Disk.INSTANCE.readBlock(sourceBlocks.get(i));
            Disk.INSTANCE.writeBlock(destinationBlocks.get(i), bytes);
        }

        FolderPane.getInstance().refresh(destinationDirectory);
        EventSystem.instance.SendEvent("showConsoleInfo", "File copied: " + sourceFilePath + " -> "
                + destinationDirectoryPath + "/" + name + "." + type);
    }

    public void typeFile(String path) throws Exception {
        checkPathForFile(path);

        File file = new File(path);
        List<Integer> blocks = file.getBlocks();
        StringBuilder stringBuilder = new StringBuilder();
        for (int block : blocks) {
            byte[] bytes = Disk.INSTANCE.readBlock(block);
            stringBuilder.append(new String(bytes));
        }
        EventSystem.instance.SendEvent("showConsoleInfo", stringBuilder.toString());
    }

    public void changeFileName(String path, String newName) throws Exception {
        checkPathForFile(path);

        File file = new File(path);
        Directory fatherDirectory = file.getFatherDirectory();

        int fileBlock = fatherDirectory.getBlockOfEntry(file);
        if (fileBlock == 0) {
            throw new MyFileSystemException("文件不存在");
        }

        removeEntry(fileBlock, file);
        file.getProperty().setName(newName);
        writeEntry(fileBlock, file);

        FolderPane.getInstance().refresh(fatherDirectory);
        EventSystem.instance.SendEvent("showConsoleInfo", "File renamed: " + path + " -> " + newName);
    }

    public void changeFileAttributes(String path, String attributesString) throws Exception {
        checkPathForFile(path);
        byte attributes = Byte.valueOf(attributesString);
        if (attributes < 0 || attributes >= DirectoryEntry.IS_DIRECTORY
                || ((attributes & DirectoryEntry.IS_COMMON_FILE) != 0
                        && (attributes & DirectoryEntry.IS_READABLE_FILE) != 0)) {
            throw new MyFileSystemException("无效属性");
        }

        File file = new File(path);
        Directory fatherDirectory = file.getFatherDirectory();

        int fileBlock = fatherDirectory.getBlockOfEntry(file);
        if (fileBlock == 0) {
            throw new MyFileSystemException("文件不存在");
        }

        removeEntry(fileBlock, file);
        file.getProperty().setAttributes(attributes);
        writeEntry(fileBlock, file);

        FolderPane.getInstance().refresh(fatherDirectory);
        EventSystem.instance.SendEvent("showConsoleInfo", "File attributes changed: " + path);
    }

    public void moveFile(String sourceFile, String destinationDirectory) throws Exception {
        copyFile(sourceFile, destinationDirectory);
        removeFile(sourceFile);
    }

    public void executeFile(String path) throws Exception {
        checkPathForFile(path);

        File file = new File(path);
        List<Integer> blocks = file.getBlocks();
        List<Byte> instructions = new ArrayList<>();
        for (int block : blocks) {
            byte[] bytes = Disk.INSTANCE.readBlock(block);
            for (byte b : bytes) {
                instructions.add(b);
            }
        }

        EventSystem.instance.SendEvent("executeStart", instructions);
    }

    public void writeFile(String path, byte[] instructions) throws Exception {
        checkPathForFile(path);

        Path filePath = new Path(path);
        File file = new File(filePath.getAbsolutePath());

        int blocksNeeded = (int) Math.ceil((double) instructions.length / Disk.INSTANCE.BLOCK_SIZE);
        List<Integer> blocks = file.getBlocks();

        if (blocks.size() < blocksNeeded) {
            List<Integer> newBlocks = FAT.INSTANCE.allocateBlock(blocksNeeded - blocks.size());
            FAT.INSTANCE.setFATItem(blocks.get(blocks.size() - 1), newBlocks.get(0));
            blocks.addAll(newBlocks);
        } else if (blocks.size() > blocksNeeded) {
            FAT.INSTANCE.freeBlock(blocks.get(blocksNeeded));
            FAT.INSTANCE.setFATItem(blocks.get(blocksNeeded - 1), FAT.INSTANCE.END_OF_FILE);
            blocks = blocks.subList(0, blocksNeeded);
        }

        int position = 0;
        for (int block : blocks) {
            byte[] bytes = new byte[Disk.INSTANCE.BLOCK_SIZE];
            System.arraycopy(instructions, position, bytes, 0, Math.min(Disk.INSTANCE.BLOCK_SIZE, instructions.length));
            Disk.INSTANCE.writeBlock(block, bytes);
            position += Disk.INSTANCE.BLOCK_SIZE;
        }

        EventSystem.instance.SendEvent("showConsoleInfo", "File written: " + path);
    }

    public void format() throws Exception {
        Disk.INSTANCE.initializeDisk();
        FolderPane.getInstance().init();
    }

    private void writeEntry(int block, DirectoryEntry newDirectoryEntry) throws Exception {
        byte[] entries = Disk.INSTANCE.readBlock(block);
        for (int i = 0; i < entries.length; i += DirectoryEntry.DIRECTORY_ENTRY_LENGTH) {
            byte[] entry = Arrays.copyOfRange(entries, i, i + DirectoryEntry.DIRECTORY_ENTRY_LENGTH);
            Property property = new Property(entry);

            DirectoryEntry directoryEntry = new DirectoryEntry(newDirectoryEntry.getPath().getAbsolutePath(), property);

            if (directoryEntry.equals(DirectoryEntry.EMPTY_DIRECTORY_ENTRY) == true) {
                System.arraycopy(newDirectoryEntry.getProperty().toBytes(), 0, entries, i,
                        DirectoryEntry.DIRECTORY_ENTRY_LENGTH);
                Disk.INSTANCE.writeBlock(block, entries);
                return;
            }
        }
    }

    private void removeEntry(int block, DirectoryEntry newDirectoryEntry) throws Exception {
        byte[] entries = Disk.INSTANCE.readBlock(block);
        for (int i = 0; i < entries.length; i += DirectoryEntry.DIRECTORY_ENTRY_LENGTH) {
            byte[] entry = Arrays.copyOfRange(entries, i, i + DirectoryEntry.DIRECTORY_ENTRY_LENGTH);
            Property property = new Property(entry);

            DirectoryEntry directoryEntry = new DirectoryEntry(newDirectoryEntry.getPath().getAbsolutePath(), property);

            if (directoryEntry.equals(newDirectoryEntry) == true) {
                System.arraycopy(DirectoryEntry.EMPTY_DIRECTORY_ENTRY.property.toBytes(), 0, entries, i,
                        DirectoryEntry.DIRECTORY_ENTRY_LENGTH);
                Disk.INSTANCE.writeBlock(block, entries);
                return;
            }
        }
    }

    private void checkPathForDirectory(String path) throws Exception {
        List<String> pathNames = new Path(path).getPathNames();
        for (String pathName : pathNames) {
            if (pathName.length() > Property.MAX_NAME_LENGTH || pathName.contains(".") ||
                    pathName.contains("$")) {
                throw new MyFileSystemException("无效路径");
            }
        }
    }

    private void checkPathForFile(String path) throws Exception {
        if (path.contains(".") == false || path.contains("/") == false) {
            throw new MyFileSystemException("无效路径");
        }

        String name = path.substring(path.lastIndexOf("/") + 1, path.lastIndexOf("."));
        String type = path.substring(path.lastIndexOf(".") + 1);
        if (name.length() > Property.MAX_NAME_LENGTH || type.length() > Property.MAX_TYPE_LENGTH
                || name.length() == 0 || type.length() == 0) {
            throw new MyFileSystemException("无效路径");
        }
    }

}
